This file is used to analyse the immune cells dataset.

library(dplyr)
library(patchwork)
library(ggplot2)
library(ComplexHeatmap)
library(org.Mm.eg.db)

.libPaths()
## [1] "/usr/local/lib/R/library"

Preparation

In this section, we set the global settings of the analysis. We will store data there :

save_name = "iblmors"
out_dir = "."

We load the dataset :

sobj = readRDS(paste0(out_dir, "/", save_name, "_sobj.rds"))
sobj
## An object of class Seurat 
## 16701 features across 3532 samples within 1 assay 
## Active assay: RNA (16701 features, 2000 variable features)
##  6 dimensional reductions calculated: RNA_pca, RNA_pca_20_tsne, RNA_pca_20_umap, harmony, harmony_20_umap, harmony_20_tsne

We load the sample information :

sample_info = readRDS(paste0(out_dir, "/../../1_metadata/hs_hd_sample_info.rds"))
project_names_oi = sample_info$project_name

graphics::pie(rep(1, nrow(sample_info)),
              col = sample_info$color,
              labels = sample_info$project_name)

Here are custom colors for each cell type :

color_markers = readRDS(paste0(out_dir, "/../../1_metadata/hs_hd_color_markers.rds"))

data.frame(cell_type = names(color_markers),
           color = unlist(color_markers)) %>%
  ggplot2::ggplot(., aes(x = cell_type, y = 0, fill = cell_type)) +
  ggplot2::geom_point(pch = 21, size = 5) +
  ggplot2::scale_fill_manual(values = unlist(color_markers), breaks = names(color_markers)) +
  ggplot2::theme_classic() +
  ggplot2::theme(legend.position = "none",
                 axis.line = element_blank(),
                 axis.title = element_blank(),
                 axis.ticks = element_blank(),
                 axis.text.y = element_blank(),
                 axis.text.x = element_text(angle = 30, hjust = 1))

This is the projection of interest :

name2D = "harmony_20_tsne"

Visualization

Gene expression

We visualize gene expression for some markers :

features = c("percent.mt", "percent.rb", "nFeature_RNA")

plot_list = lapply(features, FUN = function(one_gene) {
  Seurat::FeaturePlot(sobj, features = one_gene,
                      reduction = name2D) +
    ggplot2::theme(aspect.ratio = 1) +
    ggplot2::scale_color_gradientn(colors = aquarius::color_gene) +
    Seurat::NoAxes()
})

patchwork::wrap_plots(plot_list, ncol = 3)

Clusters

We visualize clusters :

cluster_plot = Seurat::DimPlot(sobj, reduction = name2D, label = TRUE) +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1)
cluster_plot

Cell type

We visualize cell type split by sample :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "project_name",
                                        group_by = "cell_type",
                                        split_color = setNames(sample_info$color,
                                                               nm = sample_info$project_name),
                                        group_color = color_markers,
                                        bg_pt_size = 0.5, main_pt_size = 0.5)

plot_list[[length(plot_list) + 1]] = cluster_plot

patchwork::wrap_plots(plot_list, ncol = 4) &
  Seurat::NoLegend()

Cluster type

We summarize major cell type by cluster :

cell_type_clusters = sobj@meta.data[, c("cell_type", "seurat_clusters")] %>%
  table() %>%
  prop.table(., margin = 2) %>%
  apply(., 2, which.max)
cell_type_clusters = setNames(levels(sobj$cell_type)[cell_type_clusters],
                              nm = names(cell_type_clusters))

We define cluster type :

sobj$cluster_type = cell_type_clusters[sobj$seurat_clusters] %>%
  as.factor()
table(sobj$cluster_type, sobj$cell_type)
##      
##       CD4 T cells CD8 T cells Langerhans cells macrophages B cells cuticle
##   IBL           0           0                0           0       1       1
##   ORS           2           0                0           0       3       7
##      
##       cortex medulla  IRS proliferative  IBL  ORS  IFE HFSC sebocytes
##   IBL      0       0    0             9 1465    5    7   19         3
##   ORS      2      20    7            37   26 1785   65   48        20

We subset color_markers :

color_markers = color_markers[levels(sobj$cluster_type)]

We compare cluster annotation and cell type annotation :

p1 = Seurat::DimPlot(sobj, group.by = "cell_type",
                     reduction = name2D, cols = color_markers) +
  ggplot2::labs(title = "Cell type") +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

p2 = Seurat::DimPlot(sobj, group.by = "cluster_type",
                     reduction = name2D, cols = color_markers) +
  ggplot2::labs(title = "Cluster type") +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

patchwork::wrap_plots(p1, p2, guides = "collect")

Barplot

We make a barplot to compare IBL and ORS populations in HS vs HD samples. The proportion of IBL is indicated on top of bars.

quantif = table(sobj$sample_identifier,
                sobj$cluster_type) %>%
  as.data.frame.table() %>%
  `colnames<-`(c("Sample", "cell_type", "nb_cells")) %>%
  dplyr::group_by(Sample) %>%
  dplyr::mutate(total_cells = sum(nb_cells)) %>%
  as.data.frame() %>%
  dplyr::filter(cell_type == "IBL") %>%
  dplyr::mutate(prop_ibl = nb_cells / total_cells) %>%
  dplyr::mutate(prop_ibl = 100*round(prop_ibl, 4))

sobj$seurat_clusters = factor(sobj$seurat_clusters,
                              levels = names(sort(cell_type_clusters)))

aquarius::plot_barplot(df = table(sobj$sample_identifier,
                                  sobj$seurat_clusters) %>%
                         as.data.frame.table() %>%
                         `colnames<-`(c("sample_identifier", "clusters", "nb_cells")),
                       x = "sample_identifier", y = "nb_cells", fill = "clusters",
                       position = position_fill()) +
  ggplot2::scale_fill_manual(values = c(colorRampPalette(c("chartreuse1", "chartreuse4"))(table(sort(cell_type_clusters))["IBL"]),
                                        colorRampPalette(c("cadetblue1", "cadetblue4"))(table(sort(cell_type_clusters))["ORS"])),
                             breaks = names(sort(cell_type_clusters)),
                             name = "Cell type") +
  ggplot2::geom_label(data = quantif, inherit.aes = FALSE,
                      aes(x = .data$Sample, y = 1.05, label = .data$prop_ibl),
                      label.size = 0, size = 5)

Differential expression

In this section, we perform DE between :

  • inner bulge layer (IBL) and outer root sheath (ORS) populations
  • healthy donors (HD) and HS patients (HS) within ORS population
  • healthy donors (HD) and HS patients (HS) within IBL population
  • cluster 5 vs rest of ORS, because this cluster is specific to HS compared to HD

We save the results in a list :

list_results = list()

IBL vs ORS

We change cell identities to cluster type :

Seurat::Idents(sobj) = sobj$cluster_type

table(Seurat::Idents(sobj), sobj$sample_type)
##      
##         HS   HD
##   IBL 1284  226
##   ORS 1563  459

We identify specific markers for each population :

mark = Seurat::FindMarkers(sobj, ident.1 = "IBL", ident.2 = "ORS")

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[["IBL_vs_ORS"]] = mark

dim(mark)
## [1] 1027    5
head(mark, n = 20)
##                  p_val avg_logFC pct.1 pct.2     p_val_adj
## KRT16     0.000000e+00  4.323925 0.920 0.116  0.000000e+00
## KRT6B     0.000000e+00  3.627468 0.921 0.285  0.000000e+00
## FABP5     0.000000e+00  3.348761 0.993 0.509  0.000000e+00
## KRT17     0.000000e+00  3.020736 0.987 0.733  0.000000e+00
## KRT6A    1.267920e-155  2.994730 0.764 0.476 2.117554e-151
## CST6     1.834243e-160  2.741295 0.391 0.038 3.063369e-156
## KRT6C    9.379153e-283  2.653075 0.568 0.038 1.566412e-278
## TMSB4X    0.000000e+00  2.356496 0.989 0.864  0.000000e+00
## LGALS1    0.000000e+00  2.320083 0.929 0.484  0.000000e+00
## S100A2    0.000000e+00  2.134213 0.996 0.977  0.000000e+00
## CALML3    0.000000e+00  2.003586 0.972 0.686  0.000000e+00
## GJA1      0.000000e+00  1.973282 0.875 0.586  0.000000e+00
## GJB6      0.000000e+00  1.907928 0.916 0.656  0.000000e+00
## SBSN     9.451796e-242  1.751399 0.534 0.047 1.578544e-237
## APOE      0.000000e+00  1.744652 0.983 0.772  0.000000e+00
## NDUFA4L2 1.058296e-239  1.730465 0.697 0.217 1.767461e-235
## LYPD3     0.000000e+00  1.680352 0.738 0.135  0.000000e+00
## TUBB2A    0.000000e+00  1.667800 0.756 0.181  0.000000e+00
## SDC1      0.000000e+00  1.662321 0.838 0.517  0.000000e+00
## GJB2     1.288894e-254  1.634612 0.826 0.595 2.152582e-250

There are 1027 genes differentially expressed. How many for each population ?

# IBL
mark %>%
  dplyr::filter(avg_logFC > 0) %>%
  nrow()
## [1] 434
# ORS
mark %>%
  dplyr::filter(avg_logFC < 0) %>%
  nrow()
## [1] 593

We represent the information on a figure :

mark$gene_name = rownames(mark)
mark_to_label = rbind(
  # up-regulated in IBL
  mark %>% dplyr::top_n(., n = 20, wt = avg_logFC),
  # up-regulated in ORS
  mark %>% dplyr::top_n(., n = 20, wt = -avg_logFC),
  # representative and selective for IBL
  mark %>% dplyr::top_n(., n = 20, wt = (pct.1 - pct.2)),
  # representative and selective for ORS
  mark %>% dplyr::top_n(., n = 20, wt = -(pct.1 - pct.2))) %>%
  dplyr::distinct()

ggplot2::ggplot(mark, aes(x = pct.1, y = pct.2, col = avg_logFC)) +
  ggplot2::geom_abline(slope = 1, intercept = 0, lty = 2) +
  ggplot2::geom_point() +
  ggrepel::geom_label_repel(data = mark_to_label,
                            aes(x = pct.1, y = pct.2, label = gene_name),
                            col = "black", fill = NA, size = 3, label.size = NA) +
  ggplot2::labs(title = "Differentially expressed genes",
                subtitle = "between IBL and ORS") +
  ggplot2::scale_color_gradient2(low = aquarius::color_cnv[1],
                                 mid = aquarius::color_cnv[2],
                                 high = aquarius::color_cnv[3],
                                 midpoint = 0) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5))

We represent the best genes on a violin plot, group by cluster type and split by sample type :

mark_to_label = mark_to_label %>%
  dplyr::arrange(pct.1 - pct.2)

plot_list = lapply(mark_to_label$gene_name, FUN = function(gene) {
  p = Seurat::VlnPlot(sobj, features = gene, pt.size = 0.001,
                      group.by = "cluster_type",
                      split.by = "sample_type") +
    ggplot2::scale_fill_manual(breaks = c("HS", "HD"),
                               values = c("#C55F40", "#2C78E6")) +
    ggplot2::theme(axis.title.x = element_blank(),
                   axis.title.y = element_blank(),
                   legend.position = "none")
  return(p)
})

patchwork::wrap_plots(plot_list, ncol = 5)

On the figure, we can see that some specific markers for a population are indeed specific to a sample type rather than the whole population.

Cluster 5 within ORS

We subset the Seurat object and change cell identities to sample type.

subsobj = subset(sobj, cluster_type == "ORS")
subsobj$seurat_clusters = base::droplevels(subsobj$seurat_clusters)
Seurat::Idents(subsobj) = subsobj$seurat_clusters

table(subsobj$seurat_clusters)
## 
##   0   1   3   5   7 
## 669 438 384 302 229

We identify specific markers for each population :

mark = Seurat::FindMarkers(subsobj, ident.1 = 5)

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[["cluster5_vs_ORS"]] = mark

dim(mark)
## [1] 395   5
head(mark, n = 20)
##                  p_val avg_logFC pct.1 pct.2     p_val_adj
## SLPI     7.619364e-128 1.5306935 0.765 0.164 1.272510e-123
## MT1X      4.989262e-73 1.4231367 0.954 0.680  8.332567e-69
## KRT15    2.275685e-123 1.3705217 1.000 0.874 3.800622e-119
## LY6D      1.984327e-88 1.2250993 0.503 0.080  3.314025e-84
## IGFBP3   1.494292e-140 1.1855371 0.437 0.016 2.495618e-136
## IFI27     1.438175e-99 1.1442636 0.556 0.085  2.401895e-95
## MT1E      2.040603e-70 1.0839955 0.957 0.659  3.408012e-66
## WNT3     1.095829e-212 1.0819468 0.924 0.134 1.830143e-208
## AQP3      5.658795e-83 1.0173107 0.977 0.733  9.450753e-79
## NEAT1     3.110435e-52 0.9735049 0.990 0.905  5.194737e-48
## TSC22D3   1.298633e-75 0.9601088 0.742 0.258  2.168848e-71
## MTRNR2L1  4.400300e-32 0.9329154 0.815 0.593  7.348942e-28
## CXCL14    7.450833e-63 0.9282610 0.997 0.840  1.244364e-58
## ZFP36     1.579077e-42 0.9002595 0.742 0.406  2.637216e-38
## GLUL      7.969325e-58 0.8930699 0.934 0.699  1.330957e-53
## ID1       2.365404e-17 0.8903797 0.652 0.442  3.950461e-13
## FOS       1.096048e-36 0.8789679 0.967 0.837  1.830511e-32
## IL1R2    5.014612e-122 0.8759829 0.675 0.110 8.374904e-118
## IL18     6.622739e-101 0.8511305 0.970 0.456  1.106064e-96
## HOPX      1.939070e-75 0.8395659 0.871 0.417  3.238441e-71

There are 395 genes differentially expressed. How many for each population ?

# Cluster 5
mark %>%
  dplyr::filter(avg_logFC > 0) %>%
  nrow()
## [1] 225
# Other ORS
mark %>%
  dplyr::filter(avg_logFC < 0) %>%
  nrow()
## [1] 170

We represent the information on a figure :

mark$gene_name = rownames(mark)
mark_cluster5 = rbind(
  # up-regulated in cluster 5
  mark %>% dplyr::top_n(., n = 20, wt = avg_logFC),
  # up-regulated in other ORS
  mark %>% dplyr::top_n(., n = 20, wt = -avg_logFC),
  # representative and selective for cluster 5
  mark %>% dplyr::top_n(., n = 20, wt = (pct.1 - pct.2)),
  # representative and selective for other ORS
  mark %>% dplyr::top_n(., n = 20, wt = -(pct.1 - pct.2))) %>%
  dplyr::distinct()

ggplot2::ggplot(mark, aes(x = pct.1, y = pct.2, col = avg_logFC)) +
  ggplot2::geom_abline(slope = 1, intercept = 0, lty = 2) +
  ggplot2::geom_point() +
  ggrepel::geom_label_repel(data = mark_cluster5,
                            aes(x = pct.1, y = pct.2, label = gene_name),
                            col = "black", fill = NA, size = 3, label.size = NA) +
  ggplot2::labs(title = "Differentially expressed genes",
                subtitle = "between cluster 5 and other ORS") +
  ggplot2::scale_color_gradient2(low = aquarius::color_cnv[1],
                                 mid = aquarius::color_cnv[2],
                                 high = aquarius::color_cnv[3],
                                 midpoint = 0) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5))

We represent a subset of genes on a heatmap :

features_oi = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::filter(abs(avg_logFC) > 0.5) %>%
  rownames()

length(features_oi)
## [1] 112

We prepare the scaled expression matrix :

mat_expression = Seurat::GetAssayData(subsobj, assay = "RNA", slot = "data")[features_oi, ]
mat_expression = Matrix::t(mat_expression)
mat_expression = dynutils::scale_quantile(mat_expression) # between 0 and 1
mat_expression = Matrix::t(mat_expression)
mat_expression = as.matrix(mat_expression) # not sparse

dim(mat_expression)
## [1]  112 2022

We prepare the heatmap annotation :

ha_top = ComplexHeatmap::HeatmapAnnotation(
  cluster_type = subsobj$cluster_type,
  sample_type = subsobj$sample_type,
  cluster = subsobj$seurat_clusters,
  col = list(cluster_type = color_markers,
             sample_type = setNames(nm = c("HS", "HD"),
                                    c("#C55F40", "#2C78E6")),
             cluster = setNames(nm = levels(subsobj$seurat_clusters),
                                aquarius::gg_color_hue(length(levels(subsobj$seurat_clusters))))))

And the heatmap :

ht = ComplexHeatmap::Heatmap(mat_expression,
                             col = aquarius::color_cnv,
                             # Annotation
                             top_annotation = ha_top,
                             # Grouping
                             column_title = NULL,
                             cluster_rows = TRUE,
                             cluster_columns = TRUE,
                             show_column_names = FALSE,
                             # Visual aspect
                             show_heatmap_legend = TRUE,
                             border = TRUE)

ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "bottom",
                     annotation_legend_side = "bottom")

IBL : HS vs HD

We subset the Seurat object and change cell identities to sample type.

subsobj = subset(sobj, cluster_type == "IBL")
Seurat::Idents(subsobj) = subsobj$sample_type

table(subsobj$sample_type)
## 
##   HS   HD 
## 1284  226

We identify DE genes between HS and HD :

mark = Seurat::FindMarkers(subsobj, ident.1 = "HS", ident.2 = "HD")

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[["IBL_HS_vs_HD"]] = mark

dim(mark)
## [1] 107   5
head(mark, n = 20)
##                  p_val avg_logFC pct.1 pct.2    p_val_adj
## LGALS7    1.216786e-49 1.6340915 0.685 0.190 2.032154e-45
## S100A7    2.913857e-11 1.5790394 0.289 0.080 4.866433e-07
## MIF       1.195470e-44 1.2201587 0.706 0.261 1.996554e-40
## LGALS7B   1.099708e-09 1.1642854 0.769 0.717 1.836623e-05
## MTRNR2L12 9.867130e-12 1.0580493 0.393 0.181 1.647909e-07
## MTRNR2L8  3.047443e-11 0.9124631 0.350 0.155 5.089535e-07
## MT-CO1    4.350400e-12 0.9105978 0.449 0.226 7.265603e-08
## FOSB      7.804218e-09 0.8760606 0.298 0.133 1.303382e-04
## NDUFA4L2  1.283179e-20 0.8201600 0.743 0.434 2.143037e-16
## MT-CO2    1.272279e-08 0.8121340 0.433 0.265 2.124832e-04
## S100A9    1.727393e-13 0.7487053 0.315 0.075 2.884919e-09
## KRT15     2.873193e-08 0.6857105 0.290 0.115 4.798519e-04
## CA2       1.116064e-16 0.6640743 0.460 0.173 1.863938e-12
## ARF5      3.584966e-27 0.6221800 0.433 0.044 5.987251e-23
## CKB       4.051432e-08 0.5928841 0.282 0.115 6.766297e-04
## MT-ND4    2.524958e-07 0.5633098 0.410 0.248 4.216932e-03
## MT-ATP6   1.537778e-06 0.5539256 0.496 0.358 2.568244e-02
## CST6      1.707459e-07 0.5234863 0.421 0.221 2.851627e-03
## IGKC      5.879378e-07 0.5045779 0.101 0.000 9.819150e-03
## MCL1      1.177137e-07 0.5038666 0.379 0.208 1.965937e-03

ORS : HS vs HD

We subset the Seurat object and change cell identities to sample type.

subsobj = subset(sobj, cluster_type == "ORS")
Seurat::Idents(subsobj) = subsobj$sample_type

table(subsobj$sample_type)
## 
##   HS   HD 
## 1563  459

We identify DE genes between HS and HD :

mark = Seurat::FindMarkers(subsobj, ident.1 = "HS", ident.2 = "HD")

mark = mark %>%
  dplyr::filter(p_val_adj < 0.05) %>%
  dplyr::arrange(-avg_logFC, pct.1 - pct.2)

list_results[["ORS_HS_vs_HD"]] = mark

dim(mark)
## [1] 145   5
head(mark, n = 20)
##                   p_val avg_logFC pct.1 pct.2     p_val_adj
## S100A7     1.177379e-34 1.3981213 0.390 0.083  1.966341e-30
## S100A9     8.042694e-80 1.3882495 0.831 0.346  1.343210e-75
## S100A8     6.069068e-63 1.2071418 0.784 0.353  1.013595e-58
## RPS26     8.289007e-157 1.0156704 0.971 0.950 1.384347e-152
## LGALS7B    1.751974e-26 0.9144582 0.636 0.503  2.925971e-22
## CCL2       4.001592e-35 0.8469394 0.592 0.279  6.683058e-31
## LTF        1.036558e-17 0.7369496 0.183 0.022  1.731156e-13
## MTRNR2L8   2.332458e-41 0.7044528 0.909 0.904  3.895438e-37
## IFI27      4.002962e-19 0.6856359 0.194 0.024  6.685347e-15
## TIMP1      1.883812e-25 0.6120579 0.566 0.342  3.146155e-21
## IFITM3     2.955422e-49 0.5618141 0.889 0.819  4.935850e-45
## CD74       9.221913e-27 0.5594045 0.347 0.096  1.540152e-22
## HLA-C      1.036948e-24 0.5483711 0.731 0.547  1.731807e-20
## MIF        1.151305e-28 0.5462406 0.403 0.159  1.922795e-24
## AKR1B10    7.040926e-44 0.5197609 0.372 0.033  1.175905e-39
## MTRNR2L12  1.018482e-25 0.4788943 0.951 0.943  1.700967e-21
## MTRNR2L10  1.055574e-11 0.4753293 0.589 0.538  1.762914e-07
## CXCL14     2.007187e-14 0.4495196 0.882 0.802  3.352203e-10
## LGALS7     5.171905e-12 0.4358938 0.232 0.096  8.637599e-08
## IL1R2      9.715791e-13 0.4197815 0.226 0.085  1.622634e-08

Heatmap

We represent differentially expressed genes. First, we extract all DE genes :

features_oi = c(mark_to_label$gene_name,
                rownames(list_results$IBL_HS_vs_HD),
                rownames(list_results$ORS_HS_vs_HD)) %>%
  unique()

length(features_oi)
## [1] 259

Heatmap

We prepare the scaled expression matrix :

mat_expression = Seurat::GetAssayData(sobj, assay = "RNA", slot = "data")[features_oi, ]
mat_expression = Matrix::t(mat_expression)
mat_expression = dynutils::scale_quantile(mat_expression) # between 0 and 1
mat_expression = Matrix::t(mat_expression)
mat_expression = as.matrix(mat_expression) # not sparse

dim(mat_expression)
## [1]  259 3532

We prepare the heatmap annotation :

ha_top = ComplexHeatmap::HeatmapAnnotation(
  cluster_type = sobj$cluster_type,
  sample_type = sobj$sample_type,
  cluster = sobj$seurat_clusters,
  col = list(cluster_type = color_markers,
             sample_type = setNames(nm = c("HS", "HD"),
                                    c("#C55F40", "#2C78E6")),
             cluster = setNames(nm = levels(sobj$seurat_clusters),
                                aquarius::gg_color_hue(length(levels(sobj$seurat_clusters))))))

And the heatmap :

sobj$cell_group = paste0(sobj$cluster_type, sobj$sample_type) %>%
  as.factor()

ht = ComplexHeatmap::Heatmap(mat_expression,
                             col = aquarius::color_cnv,
                             # Annotation
                             top_annotation = ha_top,
                             # Grouping
                             column_order = sobj@meta.data %>%
                               dplyr::arrange(cluster_type, sample_type, seurat_clusters) %>%
                               rownames(),
                             column_split = sobj$cell_group,
                             column_gap = unit(c(0.01, 2, 0.01), "mm"),
                             column_title = NULL,
                             cluster_rows = TRUE,
                             cluster_columns = FALSE,
                             show_column_names = FALSE,
                             # Visual aspect
                             show_heatmap_legend = TRUE,
                             border = TRUE)

ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "bottom",
                     annotation_legend_side = "bottom")

Save

We save the list of results :

saveRDS(list_results, file = paste0(out_dir, "/", save_name, "_list_results.rds"))

R Session

show
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.6 LTS
## 
## Matrix products: default
## BLAS:   /usr/local/lib/R/lib/libRblas.so
## LAPACK: /usr/local/lib/R/lib/libRlapack.so
## 
## locale:
## [1] C
## 
## attached base packages:
##  [1] parallel  stats4    grid      stats     graphics  grDevices utils    
##  [8] datasets  methods   base     
## 
## other attached packages:
##  [1] org.Mm.eg.db_3.10.0   AnnotationDbi_1.48.0  IRanges_2.20.2       
##  [4] S4Vectors_0.24.4      Biobase_2.46.0        BiocGenerics_0.32.0  
##  [7] ComplexHeatmap_2.14.0 ggplot2_3.3.5         patchwork_1.1.2      
## [10] dplyr_1.0.7          
## 
## loaded via a namespace (and not attached):
##   [1] softImpute_1.4              graphlayouts_0.7.0         
##   [3] pbapply_1.4-2               lattice_0.20-41            
##   [5] haven_2.3.1                 vctrs_0.3.8                
##   [7] usethis_2.0.1               dynwrap_1.2.1              
##   [9] blob_1.2.1                  survival_3.2-13            
##  [11] prodlim_2019.11.13          dynutils_1.0.5             
##  [13] later_1.3.0                 DBI_1.1.1                  
##  [15] R.utils_2.11.0              SingleCellExperiment_1.8.0 
##  [17] rappdirs_0.3.3              uwot_0.1.8                 
##  [19] dqrng_0.2.1                 jpeg_0.1-8.1               
##  [21] zlibbioc_1.32.0             pspline_1.0-18             
##  [23] pcaMethods_1.78.0           mvtnorm_1.1-1              
##  [25] htmlwidgets_1.5.4           GlobalOptions_0.1.2        
##  [27] future_1.22.1               UpSetR_1.4.0               
##  [29] laeken_0.5.2                leiden_0.3.3               
##  [31] clustree_0.4.3              scater_1.14.6              
##  [33] irlba_2.3.3                 DEoptimR_1.0-9             
##  [35] tidygraph_1.1.2             Rcpp_1.0.9                 
##  [37] readr_2.0.2                 KernSmooth_2.23-17         
##  [39] carrier_0.1.0               promises_1.1.0             
##  [41] gdata_2.18.0                DelayedArray_0.12.3        
##  [43] limma_3.42.2                graph_1.64.0               
##  [45] RcppParallel_5.1.4          Hmisc_4.4-0                
##  [47] fs_1.5.2                    RSpectra_0.16-0            
##  [49] fastmatch_1.1-0             ranger_0.12.1              
##  [51] digest_0.6.25               png_0.1-7                  
##  [53] sctransform_0.2.1           cowplot_1.0.0              
##  [55] DOSE_3.12.0                 here_1.0.1                 
##  [57] TInGa_0.0.0.9000            ggraph_2.0.3               
##  [59] pkgconfig_2.0.3             GO.db_3.10.0               
##  [61] DelayedMatrixStats_1.8.0    gower_0.2.1                
##  [63] ggbeeswarm_0.6.0            iterators_1.0.12           
##  [65] DropletUtils_1.6.1          reticulate_1.26            
##  [67] clusterProfiler_3.14.3      SummarizedExperiment_1.16.1
##  [69] circlize_0.4.15             beeswarm_0.4.0             
##  [71] GetoptLong_1.0.5            xfun_0.35                  
##  [73] bslib_0.3.1                 zoo_1.8-10                 
##  [75] tidyselect_1.1.0            reshape2_1.4.4             
##  [77] purrr_0.3.4                 ica_1.0-2                  
##  [79] pcaPP_1.9-73                viridisLite_0.3.0          
##  [81] rtracklayer_1.46.0          rlang_1.0.2                
##  [83] hexbin_1.28.1               jquerylib_0.1.4            
##  [85] dyneval_0.9.9               glue_1.4.2                 
##  [87] RColorBrewer_1.1-2          matrixStats_0.56.0         
##  [89] stringr_1.4.0               lava_1.6.7                 
##  [91] europepmc_0.3               DESeq2_1.26.0              
##  [93] recipes_0.1.17              labeling_0.3               
##  [95] httpuv_1.5.2                class_7.3-17               
##  [97] BiocNeighbors_1.4.2         DO.db_2.9                  
##  [99] annotate_1.64.0             jsonlite_1.7.2             
## [101] XVector_0.26.0              bit_4.0.4                  
## [103] mime_0.9                    aquarius_0.1.5             
## [105] Rsamtools_2.2.3             gridExtra_2.3              
## [107] gplots_3.0.3                stringi_1.4.6              
## [109] processx_3.5.2              gsl_2.1-6                  
## [111] bitops_1.0-6                cli_3.0.1                  
## [113] batchelor_1.2.4             RSQLite_2.2.0              
## [115] randomForest_4.6-14         tidyr_1.1.4                
## [117] data.table_1.14.2           rstudioapi_0.13            
## [119] GenomicAlignments_1.22.1    nlme_3.1-147               
## [121] qvalue_2.18.0               scran_1.14.6               
## [123] locfit_1.5-9.4              scDblFinder_1.1.8          
## [125] listenv_0.8.0               ggthemes_4.2.4             
## [127] gridGraphics_0.5-0          R.oo_1.24.0                
## [129] dbplyr_1.4.4                TTR_0.24.2                 
## [131] readxl_1.3.1                lifecycle_1.0.1            
## [133] timeDate_3043.102           ggpattern_0.3.1            
## [135] munsell_0.5.0               cellranger_1.1.0           
## [137] R.methodsS3_1.8.1           proxyC_0.1.5               
## [139] visNetwork_2.0.9            caTools_1.18.0             
## [141] codetools_0.2-16            GenomeInfoDb_1.22.1        
## [143] vipor_0.4.5                 lmtest_0.9-38              
## [145] msigdbr_7.5.1               htmlTable_1.13.3           
## [147] triebeard_0.3.0             lsei_1.2-0                 
## [149] xtable_1.8-4                ROCR_1.0-7                 
## [151] BiocManager_1.30.10         scatterplot3d_0.3-41       
## [153] abind_1.4-5                 farver_2.0.3               
## [155] parallelly_1.28.1           RANN_2.6.1                 
## [157] askpass_1.1                 GenomicRanges_1.38.0       
## [159] RcppAnnoy_0.0.16            tibble_3.1.5               
## [161] ggdendro_0.1-20             cluster_2.1.0              
## [163] future.apply_1.5.0          Seurat_3.1.5               
## [165] dendextend_1.15.1           Matrix_1.3-2               
## [167] ellipsis_0.3.2              prettyunits_1.1.1          
## [169] lubridate_1.7.9             ggridges_0.5.2             
## [171] igraph_1.2.5                RcppEigen_0.3.3.7.0        
## [173] fgsea_1.12.0                remotes_2.4.2              
## [175] scBFA_1.0.0                 destiny_3.0.1              
## [177] VIM_6.1.1                   testthat_3.1.0             
## [179] htmltools_0.5.2             BiocFileCache_1.10.2       
## [181] yaml_2.2.1                  utf8_1.1.4                 
## [183] plotly_4.9.2.1              XML_3.99-0.3               
## [185] ModelMetrics_1.2.2.2        e1071_1.7-3                
## [187] foreign_0.8-76              withr_2.5.0                
## [189] fitdistrplus_1.0-14         BiocParallel_1.20.1        
## [191] xgboost_1.4.1.1             bit64_4.0.5                
## [193] foreach_1.5.0               robustbase_0.93-9          
## [195] Biostrings_2.54.0           GOSemSim_2.13.1            
## [197] rsvd_1.0.3                  memoise_2.0.0              
## [199] evaluate_0.18               forcats_0.5.0              
## [201] rio_0.5.16                  geneplotter_1.64.0         
## [203] tzdb_0.1.2                  caret_6.0-86               
## [205] ps_1.6.0                    DiagrammeR_1.0.6.1         
## [207] curl_4.3                    fdrtool_1.2.15             
## [209] fansi_0.4.1                 highr_0.8                  
## [211] urltools_1.7.3              xts_0.12.1                 
## [213] GSEABase_1.48.0             acepack_1.4.1              
## [215] edgeR_3.28.1                checkmate_2.0.0            
## [217] scds_1.2.0                  cachem_1.0.6               
## [219] npsurv_0.4-0                babelgene_22.3             
## [221] rjson_0.2.20                openxlsx_4.1.5             
## [223] ggrepel_0.9.1               clue_0.3-60                
## [225] rprojroot_2.0.2             stabledist_0.7-1           
## [227] tools_3.6.3                 sass_0.4.0                 
## [229] nichenetr_1.1.1             magrittr_2.0.1             
## [231] RCurl_1.98-1.2              proxy_0.4-24               
## [233] car_3.0-11                  ape_5.3                    
## [235] ggplotify_0.0.5             xml2_1.3.2                 
## [237] httr_1.4.2                  assertthat_0.2.1           
## [239] rmarkdown_2.18              boot_1.3-25                
## [241] globals_0.14.0              R6_2.4.1                   
## [243] Rhdf5lib_1.8.0              nnet_7.3-14                
## [245] RcppHNSW_0.2.0              progress_1.2.2             
## [247] genefilter_1.68.0           statmod_1.4.34             
## [249] gtools_3.8.2                shape_1.4.6                
## [251] HDF5Array_1.14.4            BiocSingular_1.2.2         
## [253] rhdf5_2.30.1                splines_3.6.3              
## [255] AUCell_1.8.0                carData_3.0-4              
## [257] colorspace_1.4-1            generics_0.1.0             
## [259] base64enc_0.1-3             dynfeature_1.0.0           
## [261] smoother_1.1                gridtext_0.1.1             
## [263] pillar_1.6.3                tweenr_1.0.1               
## [265] sp_1.4-1                    ggplot.multistats_1.0.0    
## [267] rvcheck_0.1.8               GenomeInfoDbData_1.2.2     
## [269] plyr_1.8.6                  gtable_0.3.0               
## [271] zip_2.2.0                   knitr_1.41                 
## [273] latticeExtra_0.6-29         biomaRt_2.42.1             
## [275] fastmap_1.1.0               ADGofTest_0.3              
## [277] copula_1.0-0                doParallel_1.0.15          
## [279] vcd_1.4-8                   babelwhale_1.0.1           
## [281] openssl_1.4.1               scales_1.1.1               
## [283] backports_1.2.1             ipred_0.9-12               
## [285] enrichplot_1.6.1            hms_1.1.1                  
## [287] ggforce_0.3.1               Rtsne_0.15                 
## [289] shiny_1.7.1                 numDeriv_2016.8-1.1        
## [291] polyclip_1.10-0             lazyeval_0.2.2             
## [293] Formula_1.2-3               tsne_0.1-3                 
## [295] crayon_1.3.4                MASS_7.3-54                
## [297] pROC_1.16.2                 viridis_0.5.1              
## [299] dynparam_1.0.0              rpart_4.1-15               
## [301] zinbwave_1.8.0              compiler_3.6.3             
## [303] ggtext_0.1.0
LS0tCnRpdGxlOiAiSFMgcHJvamVjdCIKc3VidGl0bGU6ICJab29tIGluIElCTCBhbmQgbU9SUyBjZWxscyIKYXV0aG9yOiAiQXVkcmV5IgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclWS0lbS0lZCcpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCi0tLQoKPHN0eWxlPgpib2R5IHsKdGV4dC1hbGlnbjoganVzdGlmeX0KPC9zdHlsZT4KCjwhLS0gQXV0b21hdGljYWxseSBjb21wdXRlcyBhbmQgcHJpbnRzIGluIHRoZSBvdXRwdXQgdGhlIHJ1bm5pbmcgdGltZSBmb3IgYW55IGNvZGUgY2h1bmsgLS0+CmBgYHtyLCBlY2hvPUZBTFNFfQojIGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL3JtYXJrZG93bi9pc3N1ZXMvMTQ1Mwpob29rcyA9IGtuaXRyOjprbml0X2hvb2tzJGdldCgpCmhvb2tfZm9sZGFibGUgPSBmdW5jdGlvbih0eXBlKSB7CiAgZm9yY2UodHlwZSkKICBmdW5jdGlvbih4LCBvcHRpb25zKSB7CiAgICByZXMgPSBob29rc1tbdHlwZV1dKHgsIG9wdGlvbnMpCiAgICAKICAgIGlmIChpc0ZBTFNFKG9wdGlvbnNbW3Bhc3RlMCgiZm9sZF8iLCB0eXBlKV1dKSkgcmV0dXJuKHJlcykKICAgIAogICAgcGFzdGUwKAogICAgICAiPGRldGFpbHM+PHN1bW1hcnk+IiwgInNob3ciLCAiPC9zdW1tYXJ5PlxuXG4iLAogICAgICByZXMsCiAgICAgICJcblxuPC9kZXRhaWxzPiIKICAgICkKICB9Cn0Ka25pdHI6OmtuaXRfaG9va3Mkc2V0KAogIG91dHB1dCA9IGhvb2tfZm9sZGFibGUoIm91dHB1dCIpLAogIHBsb3QgPSBob29rX2ZvbGRhYmxlKCJwbG90IiksCiAgdGltZV9pdCA9IGxvY2FsKHsKICAgIG5vdyA9IE5VTEwKICAgIGZ1bmN0aW9uKGJlZm9yZSwgb3B0aW9ucykgewogICAgICBpZiAob3B0aW9ucyR0aW1lX2l0KSB7CiAgICAgICAgaWYgKGJlZm9yZSkgewogICAgICAgICAgbm93IDw9IFN5cy50aW1lKCkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgcmVzID0gZGlmZnRpbWUoU3lzLnRpbWUoKSwgbm93LCB1bml0cyA9ICJzZWNzIikKICAgICAgICAgIHBhc3RlKCIoVGltZSB0byBydW4gOiIsIHJvdW5kKHJlcywgZGlnaXRzID0gMiksICJzKSIpCiAgICAgICAgfQogICAgICB9CiAgICB9CiAgfSkKKQpgYGAKCjwhLS0gU2V0IGRlZmF1bHQgcGFyYW1ldGVycyBmb3IgYWxsIGNodW5rcyAtLT4KYGBge3IsIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9CnNldC5zZWVkKDEzMzdMKQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICMgZGlzcGxheSBjb2RlCiAgICAgICAgICAgICAgICAgICAgICAjIGRpc3BsYXkgY2h1bmsgb3V0cHV0CiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBmb2xkX291dHB1dCA9IEZBTFNFLCAjIHVzZWZ1bGwgZm9yIHNlc3Npb25JbmZvKCkKICAgICAgICAgICAgICAgICAgICAgIGZvbGRfcGxvdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAjIGZpZ3VyZSBzZXR0aW5ncwogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gJ2NlbnRlcicsCiAgICAgICAgICAgICAgICAgICAgICBmaWcud2lkdGggPSAyMCwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQgPSAxNSwKICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgIyBzb21ldGhpbmcgYWJvdXQgc2VlZCwgY2h1bmsgYW5kIFJtYXJrZG93biBjb21waWxhdGlvbgogICAgICAgICAgICAgICAgICAgICAgIyBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zOTQxNzAwMy9sb25nLXZlY3RvcnMtbm90LXN1cHBvcnRlZC15ZXQtZXJyb3ItaW4tcm1kLWJ1dC1ub3QtaW4tci1zY3JpcHQKICAgICAgICAgICAgICAgICAgICAgICMgY2FjaGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgY2FjaGUubGF6eSA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgIyBhZGQgcnVudGltZSBhZnRlciBjaHVuawogICAgICAgICAgICAgICAgICAgICAgdGltZV9pdCA9IEZBTFNFKQpgYGAKCgpUaGlzIGZpbGUgaXMgdXNlZCB0byBhbmFseXNlIHRoZSBpbW11bmUgY2VsbHMgZGF0YXNldC4KCmBgYHtyIGxpYnJhcnl9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkob3JnLk1tLmVnLmRiKQoKLmxpYlBhdGhzKCkKYGBgCgojIFByZXBhcmF0aW9uCgpJbiB0aGlzIHNlY3Rpb24sIHdlIHNldCB0aGUgZ2xvYmFsIHNldHRpbmdzIG9mIHRoZSBhbmFseXNpcy4gV2Ugd2lsbCBzdG9yZSBkYXRhIHRoZXJlIDoKCmBgYHtyIG91dF9kaXJ9CnNhdmVfbmFtZSA9ICJpYmxtb3JzIgpvdXRfZGlyID0gIi4iCmBgYAoKV2UgbG9hZCB0aGUgZGF0YXNldCA6CgpgYGB7ciBsb2FkX3NvYmp9CnNvYmogPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiLyIsIHNhdmVfbmFtZSwgIl9zb2JqLnJkcyIpKQpzb2JqCmBgYAoKV2UgbG9hZCB0aGUgc2FtcGxlIGluZm9ybWF0aW9uIDoKCmBgYHtyIGN1c3RvbV9wYWxldHRlX3NhbXBsZSwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDZ9CnNhbXBsZV9pbmZvID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi8uLi8xX21ldGFkYXRhL2hzX2hkX3NhbXBsZV9pbmZvLnJkcyIpKQpwcm9qZWN0X25hbWVzX29pID0gc2FtcGxlX2luZm8kcHJvamVjdF9uYW1lCgpncmFwaGljczo6cGllKHJlcCgxLCBucm93KHNhbXBsZV9pbmZvKSksCiAgICAgICAgICAgICAgY29sID0gc2FtcGxlX2luZm8kY29sb3IsCiAgICAgICAgICAgICAgbGFiZWxzID0gc2FtcGxlX2luZm8kcHJvamVjdF9uYW1lKQpgYGAKCkhlcmUgYXJlIGN1c3RvbSBjb2xvcnMgZm9yIGVhY2ggY2VsbCB0eXBlIDoKCmBgYHtyIGNvbG9yX21hcmtlcnMsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmNvbG9yX21hcmtlcnMgPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiLy4uLy4uLzFfbWV0YWRhdGEvaHNfaGRfY29sb3JfbWFya2Vycy5yZHMiKSkKCmRhdGEuZnJhbWUoY2VsbF90eXBlID0gbmFtZXMoY29sb3JfbWFya2VycyksCiAgICAgICAgICAgY29sb3IgPSB1bmxpc3QoY29sb3JfbWFya2VycykpICU+JQogIGdncGxvdDI6OmdncGxvdCguLCBhZXMoeCA9IGNlbGxfdHlwZSwgeSA9IDAsIGZpbGwgPSBjZWxsX3R5cGUpKSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChwY2ggPSAyMSwgc2l6ZSA9IDUpICsKICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSB1bmxpc3QoY29sb3JfbWFya2VycyksIGJyZWFrcyA9IG5hbWVzKGNvbG9yX21hcmtlcnMpKSArCiAgZ2dwbG90Mjo6dGhlbWVfY2xhc3NpYygpICsKICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgICAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDMwLCBoanVzdCA9IDEpKQpgYGAKClRoaXMgaXMgdGhlIHByb2plY3Rpb24gb2YgaW50ZXJlc3QgOgoKYGBge3IgbmFtZTJEfQpuYW1lMkQgPSAiaGFybW9ueV8yMF90c25lIgpgYGAKCgojIFZpc3VhbGl6YXRpb24KCiMjIEdlbmUgZXhwcmVzc2lvbgoKV2UgdmlzdWFsaXplIGdlbmUgZXhwcmVzc2lvbiBmb3Igc29tZSBtYXJrZXJzIDoKCmBgYHtyIHBsb3RfbGlzdF9mZWF0dXJlcywgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA0fQpmZWF0dXJlcyA9IGMoInBlcmNlbnQubXQiLCAicGVyY2VudC5yYiIsICJuRmVhdHVyZV9STkEiKQoKcGxvdF9saXN0ID0gbGFwcGx5KGZlYXR1cmVzLCBGVU4gPSBmdW5jdGlvbihvbmVfZ2VuZSkgewogIFNldXJhdDo6RmVhdHVyZVBsb3Qoc29iaiwgZmVhdHVyZXMgPSBvbmVfZ2VuZSwKICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCkgKwogICAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSkgKwogICAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGFxdWFyaXVzOjpjb2xvcl9nZW5lKSArCiAgICBTZXVyYXQ6Ok5vQXhlcygpCn0pCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gMykKYGBgCgojIyBDbHVzdGVycwoKV2UgdmlzdWFsaXplIGNsdXN0ZXJzIDoKCmBgYHtyIHNlZV9jbHVzdGVyaW5nLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gNH0KY2x1c3Rlcl9wbG90ID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIHJlZHVjdGlvbiA9IG5hbWUyRCwgbGFiZWwgPSBUUlVFKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSkKY2x1c3Rlcl9wbG90CmBgYAoKIyMgQ2VsbCB0eXBlCgpXZSB2aXN1YWxpemUgY2VsbCB0eXBlIHNwbGl0IGJ5IHNhbXBsZSA6CgpgYGB7ciBwbG90X3NwbGl0X2RpbXJlZCwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA3fQpwbG90X2xpc3QgPSBhcXVhcml1czo6cGxvdF9zcGxpdF9kaW1yZWQoc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0X2J5ID0gInByb2plY3RfbmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRfY29sb3IgPSBzZXROYW1lcyhzYW1wbGVfaW5mbyRjb2xvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm0gPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfY29sb3IgPSBjb2xvcl9tYXJrZXJzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmdfcHRfc2l6ZSA9IDAuNSwgbWFpbl9wdF9zaXplID0gMC41KQoKcGxvdF9saXN0W1tsZW5ndGgocGxvdF9saXN0KSArIDFdXSA9IGNsdXN0ZXJfcGxvdAoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDQpICYKICBTZXVyYXQ6Ok5vTGVnZW5kKCkKYGBgCgojIyBDbHVzdGVyIHR5cGUKCldlIHN1bW1hcml6ZSBtYWpvciBjZWxsIHR5cGUgYnkgY2x1c3RlciA6CgpgYGB7ciBjZWxsX3R5cGVfY2x1c3RlcnN9CmNlbGxfdHlwZV9jbHVzdGVycyA9IHNvYmpAbWV0YS5kYXRhWywgYygiY2VsbF90eXBlIiwgInNldXJhdF9jbHVzdGVycyIpXSAlPiUKICB0YWJsZSgpICU+JQogIHByb3AudGFibGUoLiwgbWFyZ2luID0gMikgJT4lCiAgYXBwbHkoLiwgMiwgd2hpY2gubWF4KQpjZWxsX3R5cGVfY2x1c3RlcnMgPSBzZXROYW1lcyhsZXZlbHMoc29iaiRjZWxsX3R5cGUpW2NlbGxfdHlwZV9jbHVzdGVyc10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5tID0gbmFtZXMoY2VsbF90eXBlX2NsdXN0ZXJzKSkKCmBgYAoKV2UgZGVmaW5lIGNsdXN0ZXIgdHlwZSA6CgpgYGB7ciB0YWJsZV9jbHVzdGVyX3R5cGV9CnNvYmokY2x1c3Rlcl90eXBlID0gY2VsbF90eXBlX2NsdXN0ZXJzW3NvYmokc2V1cmF0X2NsdXN0ZXJzXSAlPiUKICBhcy5mYWN0b3IoKQp0YWJsZShzb2JqJGNsdXN0ZXJfdHlwZSwgc29iaiRjZWxsX3R5cGUpCmBgYAoKV2Ugc3Vic2V0IGBjb2xvcl9tYXJrZXJzYCA6CgpgYGB7ciBzdWJzZXRfY29sb3JzfQpjb2xvcl9tYXJrZXJzID0gY29sb3JfbWFya2Vyc1tsZXZlbHMoc29iaiRjbHVzdGVyX3R5cGUpXQpgYGAKCgpXZSBjb21wYXJlIGNsdXN0ZXIgYW5ub3RhdGlvbiBhbmQgY2VsbCB0eXBlIGFubm90YXRpb24gOgoKYGBge3Igc2VlX2NsdXN0ZXJfdHlwZSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQpwMSA9IFNldXJhdDo6RGltUGxvdChzb2JqLCBncm91cC5ieSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsIGNvbHMgPSBjb2xvcl9tYXJrZXJzKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJDZWxsIHR5cGUiKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCnAyID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gImNsdXN0ZXJfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwgY29scyA9IGNvbG9yX21hcmtlcnMpICsKICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIkNsdXN0ZXIgdHlwZSIpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHAxLCBwMiwgZ3VpZGVzID0gImNvbGxlY3QiKQpgYGAKCiMjIEJhcnBsb3QKCldlIG1ha2UgYSBiYXJwbG90IHRvIGNvbXBhcmUgSUJMIGFuZCBPUlMgcG9wdWxhdGlvbnMgaW4gSFMgdnMgSEQgc2FtcGxlcy4gVGhlIHByb3BvcnRpb24gb2YgSUJMIGlzIGluZGljYXRlZCBvbiB0b3Agb2YgYmFycy4KCmBgYHtyIGJhcnBsb3RfaWJsX29ycywgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDV9CnF1YW50aWYgPSB0YWJsZShzb2JqJHNhbXBsZV9pZGVudGlmaWVyLAogICAgICAgICAgICAgICAgc29iaiRjbHVzdGVyX3R5cGUpICU+JQogIGFzLmRhdGEuZnJhbWUudGFibGUoKSAlPiUKICBgY29sbmFtZXM8LWAoYygiU2FtcGxlIiwgImNlbGxfdHlwZSIsICJuYl9jZWxscyIpKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoU2FtcGxlKSAlPiUKICBkcGx5cjo6bXV0YXRlKHRvdGFsX2NlbGxzID0gc3VtKG5iX2NlbGxzKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIGRwbHlyOjpmaWx0ZXIoY2VsbF90eXBlID09ICJJQkwiKSAlPiUKICBkcGx5cjo6bXV0YXRlKHByb3BfaWJsID0gbmJfY2VsbHMgLyB0b3RhbF9jZWxscykgJT4lCiAgZHBseXI6Om11dGF0ZShwcm9wX2libCA9IDEwMCpyb3VuZChwcm9wX2libCwgNCkpCgpzb2JqJHNldXJhdF9jbHVzdGVycyA9IGZhY3Rvcihzb2JqJHNldXJhdF9jbHVzdGVycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gbmFtZXMoc29ydChjZWxsX3R5cGVfY2x1c3RlcnMpKSkKCmFxdWFyaXVzOjpwbG90X2JhcnBsb3QoZGYgPSB0YWJsZShzb2JqJHNhbXBsZV9pZGVudGlmaWVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc29iaiRzZXVyYXRfY2x1c3RlcnMpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZS50YWJsZSgpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgYGNvbG5hbWVzPC1gKGMoInNhbXBsZV9pZGVudGlmaWVyIiwgImNsdXN0ZXJzIiwgIm5iX2NlbGxzIikpLAogICAgICAgICAgICAgICAgICAgICAgIHggPSAic2FtcGxlX2lkZW50aWZpZXIiLCB5ID0gIm5iX2NlbGxzIiwgZmlsbCA9ICJjbHVzdGVycyIsCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKCkpICsKICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKGNvbG9yUmFtcFBhbGV0dGUoYygiY2hhcnRyZXVzZTEiLCAiY2hhcnRyZXVzZTQiKSkodGFibGUoc29ydChjZWxsX3R5cGVfY2x1c3RlcnMpKVsiSUJMIl0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JSYW1wUGFsZXR0ZShjKCJjYWRldGJsdWUxIiwgImNhZGV0Ymx1ZTQiKSkodGFibGUoc29ydChjZWxsX3R5cGVfY2x1c3RlcnMpKVsiT1JTIl0pKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBuYW1lcyhzb3J0KGNlbGxfdHlwZV9jbHVzdGVycykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiQ2VsbCB0eXBlIikgKwogIGdncGxvdDI6Omdlb21fbGFiZWwoZGF0YSA9IHF1YW50aWYsIGluaGVyaXQuYWVzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IC5kYXRhJFNhbXBsZSwgeSA9IDEuMDUsIGxhYmVsID0gLmRhdGEkcHJvcF9pYmwpLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDAsIHNpemUgPSA1KQpgYGAKCiMgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24KCkluIHRoaXMgc2VjdGlvbiwgd2UgcGVyZm9ybSBERSBiZXR3ZWVuIDoKCi0gaW5uZXIgYnVsZ2UgbGF5ZXIgKElCTCkgYW5kIG91dGVyIHJvb3Qgc2hlYXRoIChPUlMpIHBvcHVsYXRpb25zCi0gaGVhbHRoeSBkb25vcnMgKEhEKSBhbmQgSFMgcGF0aWVudHMgKEhTKSB3aXRoaW4gT1JTIHBvcHVsYXRpb24KLSBoZWFsdGh5IGRvbm9ycyAoSEQpIGFuZCBIUyBwYXRpZW50cyAoSFMpIHdpdGhpbiBJQkwgcG9wdWxhdGlvbgotIGNsdXN0ZXIgNSB2cyByZXN0IG9mIE9SUywgYmVjYXVzZSB0aGlzIGNsdXN0ZXIgaXMgc3BlY2lmaWMgdG8gSFMgY29tcGFyZWQgdG8gSEQKCldlIHNhdmUgdGhlIHJlc3VsdHMgaW4gYSBsaXN0IDoKCmBgYHtyIGxpc3RfcmVzdWx0c30KbGlzdF9yZXN1bHRzID0gbGlzdCgpCmBgYAoKCiMjIElCTCB2cyBPUlMKCldlIGNoYW5nZSBjZWxsIGlkZW50aXRpZXMgdG8gY2x1c3RlciB0eXBlIDoKCmBgYHtyIGlkZW50X2NsdXN0ZXJfdHlwZX0KU2V1cmF0OjpJZGVudHMoc29iaikgPSBzb2JqJGNsdXN0ZXJfdHlwZQoKdGFibGUoU2V1cmF0OjpJZGVudHMoc29iaiksIHNvYmokc2FtcGxlX3R5cGUpCmBgYAoKV2UgaWRlbnRpZnkgc3BlY2lmaWMgbWFya2VycyBmb3IgZWFjaCBwb3B1bGF0aW9uIDoKCmBgYHtyIGRlX2NsdXN0MCwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDV9Cm1hcmsgPSBTZXVyYXQ6OkZpbmRNYXJrZXJzKHNvYmosIGlkZW50LjEgPSAiSUJMIiwgaWRlbnQuMiA9ICJPUlMiKQoKbWFyayA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6YXJyYW5nZSgtYXZnX2xvZ0ZDLCBwY3QuMSAtIHBjdC4yKQoKbGlzdF9yZXN1bHRzW1siSUJMX3ZzX09SUyJdXSA9IG1hcmsKCmRpbShtYXJrKQpoZWFkKG1hcmssIG4gPSAyMCkKYGBgCgpUaGVyZSBhcmUgYHIgbnJvdyhtYXJrKWAgZ2VuZXMgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkLiBIb3cgbWFueSBmb3IgZWFjaCBwb3B1bGF0aW9uID8KCmBgYHtyIG1ha3JfYnlfcG9wfQojIElCTAptYXJrICU+JQogIGRwbHlyOjpmaWx0ZXIoYXZnX2xvZ0ZDID4gMCkgJT4lCiAgbnJvdygpCgojIE9SUwptYXJrICU+JQogIGRwbHlyOjpmaWx0ZXIoYXZnX2xvZ0ZDIDwgMCkgJT4lCiAgbnJvdygpCmBgYAoKV2UgcmVwcmVzZW50IHRoZSBpbmZvcm1hdGlvbiBvbiBhIGZpZ3VyZSA6CgpgYGB7ciBwbG90X2RlX3BvcCwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KbWFyayRnZW5lX25hbWUgPSByb3duYW1lcyhtYXJrKQptYXJrX3RvX2xhYmVsID0gcmJpbmQoCiAgIyB1cC1yZWd1bGF0ZWQgaW4gSUJMCiAgbWFyayAlPiUgZHBseXI6OnRvcF9uKC4sIG4gPSAyMCwgd3QgPSBhdmdfbG9nRkMpLAogICMgdXAtcmVndWxhdGVkIGluIE9SUwogIG1hcmsgJT4lIGRwbHlyOjp0b3BfbiguLCBuID0gMjAsIHd0ID0gLWF2Z19sb2dGQyksCiAgIyByZXByZXNlbnRhdGl2ZSBhbmQgc2VsZWN0aXZlIGZvciBJQkwKICBtYXJrICU+JSBkcGx5cjo6dG9wX24oLiwgbiA9IDIwLCB3dCA9IChwY3QuMSAtIHBjdC4yKSksCiAgIyByZXByZXNlbnRhdGl2ZSBhbmQgc2VsZWN0aXZlIGZvciBPUlMKICBtYXJrICU+JSBkcGx5cjo6dG9wX24oLiwgbiA9IDIwLCB3dCA9IC0ocGN0LjEgLSBwY3QuMikpKSAlPiUKICBkcGx5cjo6ZGlzdGluY3QoKQoKZ2dwbG90Mjo6Z2dwbG90KG1hcmssIGFlcyh4ID0gcGN0LjEsIHkgPSBwY3QuMiwgY29sID0gYXZnX2xvZ0ZDKSkgKwogIGdncGxvdDI6Omdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoKSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gbWFya190b19sYWJlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gcGN0LjEsIHkgPSBwY3QuMiwgbGFiZWwgPSBnZW5lX25hbWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gImJsYWNrIiwgZmlsbCA9IE5BLCBzaXplID0gMywgbGFiZWwuc2l6ZSA9IE5BKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJEaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMiLAogICAgICAgICAgICAgICAgc3VidGl0bGUgPSAiYmV0d2VlbiBJQkwgYW5kIE9SUyIpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9ncmFkaWVudDIobG93ID0gYXF1YXJpdXM6OmNvbG9yX2NudlsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWlkID0gYXF1YXJpdXM6OmNvbG9yX2NudlsyXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9IGFxdWFyaXVzOjpjb2xvcl9jbnZbM10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pZHBvaW50ID0gMCkgKwogIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCmBgYAoKV2UgcmVwcmVzZW50IHRoZSBiZXN0IGdlbmVzIG9uIGEgdmlvbGluIHBsb3QsIGdyb3VwIGJ5IGNsdXN0ZXIgdHlwZSBhbmQgc3BsaXQgYnkgc2FtcGxlIHR5cGUgOgoKYGBge3IgdmlvbGluX2RlX3BvcCwgZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSAzOX0KbWFya190b19sYWJlbCA9IG1hcmtfdG9fbGFiZWwgJT4lCiAgZHBseXI6OmFycmFuZ2UocGN0LjEgLSBwY3QuMikKCnBsb3RfbGlzdCA9IGxhcHBseShtYXJrX3RvX2xhYmVsJGdlbmVfbmFtZSwgRlVOID0gZnVuY3Rpb24oZ2VuZSkgewogIHAgPSBTZXVyYXQ6OlZsblBsb3Qoc29iaiwgZmVhdHVyZXMgPSBnZW5lLCBwdC5zaXplID0gMC4wMDEsCiAgICAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJjbHVzdGVyX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICAgc3BsaXQuYnkgPSAic2FtcGxlX3R5cGUiKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbChicmVha3MgPSBjKCJIUyIsICJIRCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYygiI0M1NUY0MCIsICIjMkM3OEU2IikpICsKICAgIGdncGxvdDI6OnRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKICByZXR1cm4ocCkKfSkKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSA1KQpgYGAKCgpPbiB0aGUgZmlndXJlLCB3ZSBjYW4gc2VlIHRoYXQgc29tZSBzcGVjaWZpYyBtYXJrZXJzIGZvciBhIHBvcHVsYXRpb24gYXJlIGluZGVlZCBzcGVjaWZpYyB0byBhIHNhbXBsZSB0eXBlIHJhdGhlciB0aGFuIHRoZSB3aG9sZSBwb3B1bGF0aW9uLgoKIyMgQ2x1c3RlciA1IHdpdGhpbiBPUlMKCldlIHN1YnNldCB0aGUgU2V1cmF0IG9iamVjdCBhbmQgY2hhbmdlIGNlbGwgaWRlbnRpdGllcyB0byBzYW1wbGUgdHlwZS4KCmBgYHtyIHN1Yl9jbHVzdGVyNX0Kc3Vic29iaiA9IHN1YnNldChzb2JqLCBjbHVzdGVyX3R5cGUgPT0gIk9SUyIpCnN1YnNvYmokc2V1cmF0X2NsdXN0ZXJzID0gYmFzZTo6ZHJvcGxldmVscyhzdWJzb2JqJHNldXJhdF9jbHVzdGVycykKU2V1cmF0OjpJZGVudHMoc3Vic29iaikgPSBzdWJzb2JqJHNldXJhdF9jbHVzdGVycwoKdGFibGUoc3Vic29iaiRzZXVyYXRfY2x1c3RlcnMpCmBgYAoKV2UgaWRlbnRpZnkgc3BlY2lmaWMgbWFya2VycyBmb3IgZWFjaCBwb3B1bGF0aW9uIDoKCmBgYHtyIGRlX2NsdXN0NSwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDV9Cm1hcmsgPSBTZXVyYXQ6OkZpbmRNYXJrZXJzKHN1YnNvYmosIGlkZW50LjEgPSA1KQoKbWFyayA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6YXJyYW5nZSgtYXZnX2xvZ0ZDLCBwY3QuMSAtIHBjdC4yKQoKbGlzdF9yZXN1bHRzW1siY2x1c3RlcjVfdnNfT1JTIl1dID0gbWFyawoKZGltKG1hcmspCmhlYWQobWFyaywgbiA9IDIwKQpgYGAKClRoZXJlIGFyZSBgciBucm93KG1hcmspYCBnZW5lcyBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQuIEhvdyBtYW55IGZvciBlYWNoIHBvcHVsYXRpb24gPwoKYGBge3IgbWFya19jbHVzdGVyNX0KIyBDbHVzdGVyIDUKbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKGF2Z19sb2dGQyA+IDApICU+JQogIG5yb3coKQoKIyBPdGhlciBPUlMKbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKGF2Z19sb2dGQyA8IDApICU+JQogIG5yb3coKQpgYGAKCldlIHJlcHJlc2VudCB0aGUgaW5mb3JtYXRpb24gb24gYSBmaWd1cmUgOgoKYGBge3IgcGxvdF9kZV9jbHVzdGVyNSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KbWFyayRnZW5lX25hbWUgPSByb3duYW1lcyhtYXJrKQptYXJrX2NsdXN0ZXI1ID0gcmJpbmQoCiAgIyB1cC1yZWd1bGF0ZWQgaW4gY2x1c3RlciA1CiAgbWFyayAlPiUgZHBseXI6OnRvcF9uKC4sIG4gPSAyMCwgd3QgPSBhdmdfbG9nRkMpLAogICMgdXAtcmVndWxhdGVkIGluIG90aGVyIE9SUwogIG1hcmsgJT4lIGRwbHlyOjp0b3BfbiguLCBuID0gMjAsIHd0ID0gLWF2Z19sb2dGQyksCiAgIyByZXByZXNlbnRhdGl2ZSBhbmQgc2VsZWN0aXZlIGZvciBjbHVzdGVyIDUKICBtYXJrICU+JSBkcGx5cjo6dG9wX24oLiwgbiA9IDIwLCB3dCA9IChwY3QuMSAtIHBjdC4yKSksCiAgIyByZXByZXNlbnRhdGl2ZSBhbmQgc2VsZWN0aXZlIGZvciBvdGhlciBPUlMKICBtYXJrICU+JSBkcGx5cjo6dG9wX24oLiwgbiA9IDIwLCB3dCA9IC0ocGN0LjEgLSBwY3QuMikpKSAlPiUKICBkcGx5cjo6ZGlzdGluY3QoKQoKZ2dwbG90Mjo6Z2dwbG90KG1hcmssIGFlcyh4ID0gcGN0LjEsIHkgPSBwY3QuMiwgY29sID0gYXZnX2xvZ0ZDKSkgKwogIGdncGxvdDI6Omdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoKSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChkYXRhID0gbWFya19jbHVzdGVyNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gcGN0LjEsIHkgPSBwY3QuMiwgbGFiZWwgPSBnZW5lX25hbWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gImJsYWNrIiwgZmlsbCA9IE5BLCBzaXplID0gMywgbGFiZWwuc2l6ZSA9IE5BKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJEaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMiLAogICAgICAgICAgICAgICAgc3VidGl0bGUgPSAiYmV0d2VlbiBjbHVzdGVyIDUgYW5kIG90aGVyIE9SUyIpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9ncmFkaWVudDIobG93ID0gYXF1YXJpdXM6OmNvbG9yX2NudlsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWlkID0gYXF1YXJpdXM6OmNvbG9yX2NudlsyXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9IGFxdWFyaXVzOjpjb2xvcl9jbnZbM10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pZHBvaW50ID0gMCkgKwogIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCmBgYAoKV2UgcmVwcmVzZW50IGEgc3Vic2V0IG9mIGdlbmVzIG9uIGEgaGVhdG1hcCA6CgpgYGB7ciBwcmVwX2dlbmVzX2NsdXN0ZXI1fQpmZWF0dXJlc19vaSA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6ZmlsdGVyKGFicyhhdmdfbG9nRkMpID4gMC41KSAlPiUKICByb3duYW1lcygpCgpsZW5ndGgoZmVhdHVyZXNfb2kpCmBgYAoKV2UgcHJlcGFyZSB0aGUgc2NhbGVkIGV4cHJlc3Npb24gbWF0cml4IDoKCmBgYHtyIG1hdF9jbHVzdGVyNX0KbWF0X2V4cHJlc3Npb24gPSBTZXVyYXQ6OkdldEFzc2F5RGF0YShzdWJzb2JqLCBhc3NheSA9ICJSTkEiLCBzbG90ID0gImRhdGEiKVtmZWF0dXJlc19vaSwgXQptYXRfZXhwcmVzc2lvbiA9IE1hdHJpeDo6dChtYXRfZXhwcmVzc2lvbikKbWF0X2V4cHJlc3Npb24gPSBkeW51dGlsczo6c2NhbGVfcXVhbnRpbGUobWF0X2V4cHJlc3Npb24pICMgYmV0d2VlbiAwIGFuZCAxCm1hdF9leHByZXNzaW9uID0gTWF0cml4Ojp0KG1hdF9leHByZXNzaW9uKQptYXRfZXhwcmVzc2lvbiA9IGFzLm1hdHJpeChtYXRfZXhwcmVzc2lvbikgIyBub3Qgc3BhcnNlCgpkaW0obWF0X2V4cHJlc3Npb24pCmBgYAoKV2UgcHJlcGFyZSB0aGUgaGVhdG1hcCBhbm5vdGF0aW9uIDoKCmBgYHtyIGhhX3RvcF9jbHVzdGVyNX0KaGFfdG9wID0gQ29tcGxleEhlYXRtYXA6OkhlYXRtYXBBbm5vdGF0aW9uKAogIGNsdXN0ZXJfdHlwZSA9IHN1YnNvYmokY2x1c3Rlcl90eXBlLAogIHNhbXBsZV90eXBlID0gc3Vic29iaiRzYW1wbGVfdHlwZSwKICBjbHVzdGVyID0gc3Vic29iaiRzZXVyYXRfY2x1c3RlcnMsCiAgY29sID0gbGlzdChjbHVzdGVyX3R5cGUgPSBjb2xvcl9tYXJrZXJzLAogICAgICAgICAgICAgc2FtcGxlX3R5cGUgPSBzZXROYW1lcyhubSA9IGMoIkhTIiwgIkhEIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoIiNDNTVGNDAiLCAiIzJDNzhFNiIpKSwKICAgICAgICAgICAgIGNsdXN0ZXIgPSBzZXROYW1lcyhubSA9IGxldmVscyhzdWJzb2JqJHNldXJhdF9jbHVzdGVycyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXF1YXJpdXM6OmdnX2NvbG9yX2h1ZShsZW5ndGgobGV2ZWxzKHN1YnNvYmokc2V1cmF0X2NsdXN0ZXJzKSkpKSkpCmBgYAoKQW5kIHRoZSBoZWF0bWFwIDoKCmBgYHtyIGhlYXRtYXBfY2x1c3RlcjUsIGZpZy53aWR0aCA9IDE1LCBmaWcuaGVpZ2h0ID0gMzB9Cmh0ID0gQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAobWF0X2V4cHJlc3Npb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gYXF1YXJpdXM6OmNvbG9yX2NudiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEFubm90YXRpb24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3BfYW5ub3RhdGlvbiA9IGhhX3RvcCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEdyb3VwaW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX3RpdGxlID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3Jvd3MgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19jb2x1bW5fbmFtZXMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFZpc3VhbCBhc3BlY3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2hlYXRtYXBfbGVnZW5kID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXIgPSBUUlVFKQoKQ29tcGxleEhlYXRtYXA6OmRyYXcoaHQsCiAgICAgICAgICAgICAgICAgICAgIG1lcmdlX2xlZ2VuZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAiYm90dG9tIiwKICAgICAgICAgICAgICAgICAgICAgYW5ub3RhdGlvbl9sZWdlbmRfc2lkZSA9ICJib3R0b20iKQpgYGAKCgojIyBJQkwgOiBIUyB2cyBIRAoKV2Ugc3Vic2V0IHRoZSBTZXVyYXQgb2JqZWN0IGFuZCBjaGFuZ2UgY2VsbCBpZGVudGl0aWVzIHRvIHNhbXBsZSB0eXBlLgoKYGBge3Igc3ViX0lCTH0Kc3Vic29iaiA9IHN1YnNldChzb2JqLCBjbHVzdGVyX3R5cGUgPT0gIklCTCIpClNldXJhdDo6SWRlbnRzKHN1YnNvYmopID0gc3Vic29iaiRzYW1wbGVfdHlwZQoKdGFibGUoc3Vic29iaiRzYW1wbGVfdHlwZSkKYGBgCgpXZSBpZGVudGlmeSBERSBnZW5lcyBiZXR3ZWVuIEhTIGFuZCBIRCA6CgpgYGB7ciBkZV9pYmwsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSA1fQptYXJrID0gU2V1cmF0OjpGaW5kTWFya2VycyhzdWJzb2JqLCBpZGVudC4xID0gIkhTIiwgaWRlbnQuMiA9ICJIRCIpCgptYXJrID0gbWFyayAlPiUKICBkcGx5cjo6ZmlsdGVyKHBfdmFsX2FkaiA8IDAuMDUpICU+JQogIGRwbHlyOjphcnJhbmdlKC1hdmdfbG9nRkMsIHBjdC4xIC0gcGN0LjIpCgpsaXN0X3Jlc3VsdHNbWyJJQkxfSFNfdnNfSEQiXV0gPSBtYXJrCgpkaW0obWFyaykKaGVhZChtYXJrLCBuID0gMjApCmBgYAoKIyMgT1JTIDogSFMgdnMgSEQKCldlIHN1YnNldCB0aGUgU2V1cmF0IG9iamVjdCBhbmQgY2hhbmdlIGNlbGwgaWRlbnRpdGllcyB0byBzYW1wbGUgdHlwZS4KCmBgYHtyIHN1Yl9PUlN9CnN1YnNvYmogPSBzdWJzZXQoc29iaiwgY2x1c3Rlcl90eXBlID09ICJPUlMiKQpTZXVyYXQ6OklkZW50cyhzdWJzb2JqKSA9IHN1YnNvYmokc2FtcGxlX3R5cGUKCnRhYmxlKHN1YnNvYmokc2FtcGxlX3R5cGUpCmBgYAoKV2UgaWRlbnRpZnkgREUgZ2VuZXMgYmV0d2VlbiBIUyBhbmQgSEQgOgoKYGBge3IgZGVfb3JzLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNX0KbWFyayA9IFNldXJhdDo6RmluZE1hcmtlcnMoc3Vic29iaiwgaWRlbnQuMSA9ICJIUyIsIGlkZW50LjIgPSAiSEQiKQoKbWFyayA9IG1hcmsgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KSAlPiUKICBkcGx5cjo6YXJyYW5nZSgtYXZnX2xvZ0ZDLCBwY3QuMSAtIHBjdC4yKQoKbGlzdF9yZXN1bHRzW1siT1JTX0hTX3ZzX0hEIl1dID0gbWFyawoKZGltKG1hcmspCmhlYWQobWFyaywgbiA9IDIwKQpgYGAKCgojIEhlYXRtYXAKCldlIHJlcHJlc2VudCBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMuIEZpcnN0LCB3ZSBleHRyYWN0IGFsbCBERSBnZW5lcyA6CgpgYGB7ciBwcmVwX2dlbmVzfQpmZWF0dXJlc19vaSA9IGMobWFya190b19sYWJlbCRnZW5lX25hbWUsCiAgICAgICAgICAgICAgICByb3duYW1lcyhsaXN0X3Jlc3VsdHMkSUJMX0hTX3ZzX0hEKSwKICAgICAgICAgICAgICAgIHJvd25hbWVzKGxpc3RfcmVzdWx0cyRPUlNfSFNfdnNfSEQpKSAlPiUKICB1bmlxdWUoKQoKbGVuZ3RoKGZlYXR1cmVzX29pKQpgYGAKCiMjIEhlYXRtYXAKCldlIHByZXBhcmUgdGhlIHNjYWxlZCBleHByZXNzaW9uIG1hdHJpeCA6CgpgYGB7ciBtYXR9Cm1hdF9leHByZXNzaW9uID0gU2V1cmF0OjpHZXRBc3NheURhdGEoc29iaiwgYXNzYXkgPSAiUk5BIiwgc2xvdCA9ICJkYXRhIilbZmVhdHVyZXNfb2ksIF0KbWF0X2V4cHJlc3Npb24gPSBNYXRyaXg6OnQobWF0X2V4cHJlc3Npb24pCm1hdF9leHByZXNzaW9uID0gZHludXRpbHM6OnNjYWxlX3F1YW50aWxlKG1hdF9leHByZXNzaW9uKSAjIGJldHdlZW4gMCBhbmQgMQptYXRfZXhwcmVzc2lvbiA9IE1hdHJpeDo6dChtYXRfZXhwcmVzc2lvbikKbWF0X2V4cHJlc3Npb24gPSBhcy5tYXRyaXgobWF0X2V4cHJlc3Npb24pICMgbm90IHNwYXJzZQoKZGltKG1hdF9leHByZXNzaW9uKQpgYGAKCldlIHByZXBhcmUgdGhlIGhlYXRtYXAgYW5ub3RhdGlvbiA6CgpgYGB7ciBoYV90b3B9CmhhX3RvcCA9IENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwQW5ub3RhdGlvbigKICBjbHVzdGVyX3R5cGUgPSBzb2JqJGNsdXN0ZXJfdHlwZSwKICBzYW1wbGVfdHlwZSA9IHNvYmokc2FtcGxlX3R5cGUsCiAgY2x1c3RlciA9IHNvYmokc2V1cmF0X2NsdXN0ZXJzLAogIGNvbCA9IGxpc3QoY2x1c3Rlcl90eXBlID0gY29sb3JfbWFya2VycywKICAgICAgICAgICAgIHNhbXBsZV90eXBlID0gc2V0TmFtZXMobm0gPSBjKCJIUyIsICJIRCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCIjQzU1RjQwIiwgIiMyQzc4RTYiKSksCiAgICAgICAgICAgICBjbHVzdGVyID0gc2V0TmFtZXMobm0gPSBsZXZlbHMoc29iaiRzZXVyYXRfY2x1c3RlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFxdWFyaXVzOjpnZ19jb2xvcl9odWUobGVuZ3RoKGxldmVscyhzb2JqJHNldXJhdF9jbHVzdGVycykpKSkpKQpgYGAKCkFuZCB0aGUgaGVhdG1hcCA6CgpgYGB7ciBoZWF0bWFwLCBmaWcud2lkdGggPSAxNSwgZmlnLmhlaWdodCA9IDMwfQpzb2JqJGNlbGxfZ3JvdXAgPSBwYXN0ZTAoc29iaiRjbHVzdGVyX3R5cGUsIHNvYmokc2FtcGxlX3R5cGUpICU+JQogIGFzLmZhY3RvcigpCgpodCA9IENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKG1hdF9leHByZXNzaW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbCA9IGFxdWFyaXVzOjpjb2xvcl9jbnYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBbm5vdGF0aW9uCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wX2Fubm90YXRpb24gPSBoYV90b3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBHcm91cGluZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9vcmRlciA9IHNvYmpAbWV0YS5kYXRhICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmFycmFuZ2UoY2x1c3Rlcl90eXBlLCBzYW1wbGVfdHlwZSwgc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd25hbWVzKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX3NwbGl0ID0gc29iaiRjZWxsX2dyb3VwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9nYXAgPSB1bml0KGMoMC4wMSwgMiwgMC4wMSksICJtbSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl90aXRsZSA9IE5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvbHVtbl9uYW1lcyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVmlzdWFsIGFzcGVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfaGVhdG1hcF9sZWdlbmQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlciA9IFRSVUUpCgpDb21wbGV4SGVhdG1hcDo6ZHJhdyhodCwKICAgICAgICAgICAgICAgICAgICAgbWVyZ2VfbGVnZW5kID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJib3R0b20iLAogICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2xlZ2VuZF9zaWRlID0gImJvdHRvbSIpCmBgYAoKIyBTYXZlCgpXZSBzYXZlIHRoZSBsaXN0IG9mIHJlc3VsdHMgOgoKYGBge3Igc2F2ZV9saXN0X3Jlc3VsdHN9CnNhdmVSRFMobGlzdF9yZXN1bHRzLCBmaWxlID0gcGFzdGUwKG91dF9kaXIsICIvIiwgc2F2ZV9uYW1lLCAiX2xpc3RfcmVzdWx0cy5yZHMiKSkKYGBgCgoKIyBSIFNlc3Npb24KCmBgYHtyIHNlc3Npb25pbmZvLCBlY2hvID0gRkFMU0UsIGZvbGRfb3V0cHV0ID0gVFJVRX0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==